#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <network/dual_stack_socket.h>

#define INVALID_SOCKET -1
#define ERR_OUT_FD stderr

struct dualstack_socket_s
{
    enum address_type type;
    struct hostent *he;
    struct sockaddr_in ipv4addr;
    struct sockaddr_in6 ipv6addr;
    struct sockaddr* addr;
    char address[128];
    int sock;
};

int get_socket_fd(dualstack_socket_t s)
{
    if (s)
    {
        return s->sock;
    }
    else
    {
        return INVALID_SOCKET;
    }
}

int get_sockaddr_len(dualstack_socket_t s)
{
    if (s)
    {
        if (addr_type_ipv4 == s->type)
        {
            return sizeof(struct sockaddr_in);
        }
        else
        {
            return sizeof(struct sockaddr_in6);
        }
    }
    else
    {
        fprintf(ERR_OUT_FD, "Invalid socket pointer! %s %u\n", __FUNCTION__, __LINE__);
    }
}


struct sockaddr* get_sockaddr(dualstack_socket_t s)
{
    if (s)
    {
        return s->addr;
    }
    else
    {
        fprintf(ERR_OUT_FD, "Invalid socket pointer! %s %u\n", __FUNCTION__, __LINE__);
        return NULL;
    }
}


dualstack_socket_t create_dual_socket(const char* address, unsigned int port, int protocal)
{
    dualstack_socket_t ds_socket = NULL;
    if (NULL == address)
    {
        fprintf(ERR_OUT_FD, "Invalid address pointer!\n");
        return NULL;
    }

    ds_socket = (dualstack_socket_t)calloc(1, sizeof(struct dualstack_socket_s));

    if (NULL == ds_socket)
    {
        fprintf(ERR_OUT_FD, "Malloc failed! %s %u\n", __FUNCTION__, __LINE__);
        return NULL;
    }

    strncpy(ds_socket->address, address, sizeof(ds_socket->address) - 1);
    if (memchr(address, '.', strlen(address)))
    {
        ds_socket->type = addr_type_ipv4;
    }
    else if (memchr(address, ':', strlen(address)))
    {
        ds_socket->type = addr_type_ipv6;
    }
    else
    {
        fprintf(ERR_OUT_FD, "Unknown address type %s!\n", address);
        return NULL;
    }


    if (addr_type_ipv4 == ds_socket->type)
    {
        ds_socket->addr = (struct sockaddr*)&(ds_socket->ipv4addr);
        ds_socket->ipv4addr.sin_family = AF_INET;
        ds_socket->ipv4addr.sin_port = htons(port);
        if(inet_aton(address, &(ds_socket->ipv4addr.sin_addr)) < 0)
        {
            fprintf(ERR_OUT_FD, "[%s] is not a valid IPV4 address\n", address);
            free(ds_socket);
            return NULL;
        }

        if (protocol_type_tcp == protocal)
        {
            ds_socket->sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        }
        else if (protocol_type_udp == protocal)
        {
            ds_socket->sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
        }
        else
        {
            fprintf(ERR_OUT_FD, "[%d] is undefined! %s %u\n", protocal, __FUNCTION__, __LINE__);
            free(ds_socket);
            return NULL;
        }

        if (INVALID_SOCKET == ds_socket->sock)
        {
            fprintf(ERR_OUT_FD, "Create ipv4 socket failed! %s %u\n", protocal, __FUNCTION__, __LINE__);
            free(ds_socket);
            return NULL;
        }

    }

    if (addr_type_ipv6 == ds_socket->type)
    {
        ds_socket->addr = (struct sockaddr*)&ds_socket->ipv6addr;
        ds_socket->ipv6addr.sin6_family = AF_INET6;
        ds_socket->ipv6addr.sin6_port = htons(port);
        if (inet_pton(AF_INET6, address, &(ds_socket->ipv6addr.sin6_addr)) < 0 )
        {
            fprintf(ERR_OUT_FD, "[%s] is not a valid IPV6 address\n", address);
            free(ds_socket);
            return NULL;
        }

        if (protocol_type_tcp == protocal)
        {
            ds_socket->sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
        }
        else if (protocol_type_udp == protocal)
        {
            ds_socket->sock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
        }
        else
        {
            fprintf(ERR_OUT_FD, "[%d] is undefined! %s %u\n", protocal, __FUNCTION__, __LINE__);
            free(ds_socket);
            return NULL;
        }

        if (INVALID_SOCKET == ds_socket->sock)
        {
            fprintf(ERR_OUT_FD, "Create ipv6 socket failed! %s %u\n", protocal, __FUNCTION__, __LINE__);
            free(ds_socket);
            return NULL;
        }
    }


    return ds_socket;
}

int destroy_dual_socket(dualstack_socket_t sock)
{
	if (sock)
	{
		close(sock->sock);
		free(sock);
	}
    return 0;
}

